工具调用
Function Calling概述
Function Calling(函数调用/工具调用)是Agent实现的关键能力,允许LLM根据用户意图调用外部工具或API。
┌─────────────────────────────────────────────────────────────┐
│ Function Calling 流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户: "北京今天天气怎么样?" │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ LLM │ │
│ │ 识别意图 → 需要查询天气 → 生成函数调用请求 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ LLM输出: │
│ { │
│ "tool": "get_weather", │
│ "args": {"city": "北京"} │
│ } │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 工具执行 │ │
│ │ 调用 weather_api.get_weather(city="北京") │ │
│ │ 返回: {temp: 25, condition: "晴"} │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ LLM │ │
│ │ 整合工具返回 → 生成自然语言回答 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 用户: "北京今天天气晴朗,气温25°C" │
│ │
└─────────────────────────────────────────────────────────────┘OpenAI Function Calling
基础实现
javascript
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
// 定义可用工具
const tools = [
{
type: 'function',
function: {
name: 'get_weather',
description: '获取指定城市的天气信息',
parameters: {
type: 'object',
properties: {
city: {
type: 'string',
description: '城市名称,如"北京"、"上海"'
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: '温度单位'
}
},
required: ['city']
}
}
},
{
type: 'function',
function: {
name: 'get_stock_price',
description: '获取股票当前价格',
parameters: {
type: 'object',
properties: {
symbol: {
type: 'string',
description: '股票代码,如"AAPL"、"TSLA"'
}
},
required: ['symbol']
}
}
}
];
// 执行对话
async function chatWithTools(userMessage) {
const messages = [
{ role: 'user', content: userMessage }
];
// 第一次调用:让LLM决定是否调用工具
const response = await client.chat.completions.create({
model: 'gpt-4-turbo',
messages: messages,
tools: tools,
tool_choice: 'auto' // 自动选择工具
});
const assistantMessage = response.choices[0].message;
// 检查是否有函数调用
if (assistantMessage.tool_calls) {
messages.push(assistantMessage);
// 执行所有工具调用
for (const toolCall of assistantMessage.tool_calls) {
const toolName = toolCall.function.name;
const args = JSON.parse(toolCall.function.arguments);
// 调用工具
const result = await executeTool(toolName, args);
// 添加工具返回
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result)
});
}
// 第二次调用:让LLM基于工具结果生成回答
const finalResponse = await client.chat.completions.create({
model: 'gpt-4-turbo',
messages: messages,
tools: tools
});
return finalResponse.choices[0].message.content;
}
return assistantMessage.content;
}
// 工具执行器
async function executeTool(name, args) {
switch (name) {
case 'get_weather':
return await getWeather(args.city, args.unit);
case 'get_stock_price':
return await getStockPrice(args.symbol);
default:
throw new Error(`Unknown tool: ${name}`);
}
}
// 工具实现
async function getWeather(city, unit = 'celsius') {
// 实际项目中调用天气API
return {
city: city,
temperature: unit === 'celsius' ? 25 : 77,
condition: '晴',
humidity: 45
};
}Python实现
python
from openai import OpenAI
client = OpenAI()
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
}
]
messages = [{"role": "user", "content": "北京今天天气如何?"}]
# 第一次调用
response = client.chat.completions.create(
model="gpt-4-turbo",
messages=messages,
tools=tools,
tool_choice="auto"
)
assistant_msg = response.choices[0].message
if assistant_msg.tool_calls:
messages.append(assistant_msg)
for tool_call in assistant_msg.tool_calls:
tool_name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
# 执行工具
result = execute_tool(tool_name, args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
# 第二次调用
final_response = client.chat.completions.create(
model="gpt-4-turbo",
messages=messages,
tools=tools
)
print(final_response.choices[0].message.content)工具设计原则
1. 清晰的描述
javascript
// ❌ 模糊的描述
{
"name": "search",
"description": "Search for something"
}
// ✅ 清晰的描述
{
"name": "search_products",
"description": "搜索电商平台商品,支持按名称、价格、品牌筛选,返回商品列表及其详细信息"
}2. 明确的参数
javascript
// ❌ 参数无约束
{
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"}
}
}
}
// ✅ 参数有约束和说明
{
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词,支持空格分隔多词搜索"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "food", "books"],
"description": "商品分类"
},
"max_price": {
"type": "number",
"description": "最高价格,单位元",
"minimum": 0
}
},
"required": ["query"]
}
}3. 返回格式规范
python
def format_tool_response(success: bool, data: Any = None, error: str = None):
"""
规范工具返回格式
"""
return {
"success": success,
"data": data,
"error": error,
"timestamp": datetime.now().isoformat()
}
# 使用
async def search_products(query, category=None):
try:
results = await do_search(query, category)
return format_tool_response(True, results)
except Exception as e:
return format_tool_response(False, error=str(e))工具类型示例
1. 搜索工具
javascript
const searchTool = {
type: 'function',
function: {
name: 'web_search',
description: '使用搜索引擎搜索互联网,获取相关信息',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: '搜索关键词'
},
num_results: {
type: 'integer',
description: '返回结果数量',
default: 5,
minimum: 1,
maximum: 10
}
},
required: ['query']
}
}
};
async function webSearch(query, numResults = 5) {
// 调用搜索API
const results = await searchAPI.search(query, { num: numResults });
return results.map(r => ({
title: r.title,
url: r.url,
snippet: r.snippet
}));
}2. 数据库查询工具
javascript
const dbQueryTool = {
type: 'function',
function: {
name: 'query_database',
description: '执行SQL查询,从数据库获取数据',
parameters: {
type: 'object',
properties: {
sql: {
type: 'string',
description: 'SQL SELECT语句,只支持查询操作'
},
limit: {
type: 'integer',
description: '返回记录数上限',
default: 100
}
},
required: ['sql']
}
}
};
// 安全的数据库查询
async function queryDatabase(sql, limit = 100) {
// 1. SQL注入检查
if (!isSafeSQL(sql)) {
throw new Error('禁止的SQL语句');
}
// 2. 只允许SELECT
if (!sql.trim().toUpperCase().startsWith('SELECT')) {
throw new Error('只支持SELECT查询');
}
// 3. 添加LIMIT
if (!sql.includes('LIMIT')) {
sql = `${sql} LIMIT ${limit}`;
}
const results = await db.query(sql);
return results;
}3. API调用工具
javascript
const apiCallTool = {
type: 'function',
function: {
name: 'call_api',
description: '调用HTTP API接口获取数据',
parameters: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'API完整URL地址'
},
method: {
type: 'string',
enum: ['GET', 'POST'],
default: 'GET'
},
headers: {
type: 'object',
description: 'HTTP请求头'
},
body: {
type: 'string',
description: 'POST请求体(JSON字符串)'
}
},
required: ['url']
}
}
};
async function callAPI(url, method = 'GET', headers = {}, body = null) {
const options = {
method,
headers: {
'Content-Type': 'application/json',
...headers
}
};
if (body && method === 'POST') {
options.body = body;
}
const response = await fetch(url, options);
return await response.json();
}4. 文件操作工具
javascript
const fileTools = [
{
type: 'function',
function: {
name: 'read_file',
description: '读取文件内容',
parameters: {
type: 'object',
properties: {
path: {
type: 'string',
description: '文件路径'
},
encoding: {
type: 'string',
default: 'utf-8'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'write_file',
description: '写入内容到文件',
parameters: {
type: 'object',
properties: {
path: {
type: 'string',
description: '文件路径'
},
content: {
type: 'string',
description: '文件内容'
}
},
required: ['path', 'content']
}
}
}
];工具调用最佳实践
1. 错误处理
javascript
async function safeToolExecution(toolName, args) {
try {
const result = await executeTool(toolName, args);
return {
success: true,
result
};
} catch (error) {
// 详细记录错误
console.error(`Tool ${toolName} failed:`, error);
return {
success: false,
error: error.message,
// 可以返回降级结果
fallback: getFallbackResult(toolName)
};
}
}2. 工具选择策略
javascript
// 指定必须使用的工具
await client.chat.completions.create({
model: 'gpt-4-turbo',
messages: messages,
tools: tools,
tool_choice: {
type: 'function',
function: { name: 'get_weather' } // 强制使用天气工具
}
});
// 或让模型自动选择
await client.chat.completions.create({
model: 'gpt-4-turbo',
messages: messages,
tools: tools,
tool_choice: 'auto' // 自动选择
});3. 并行工具调用
javascript
// 支持同时调用多个工具
const response = await client.chat.completions.create({
model: 'gpt-4-turbo',
messages: messages,
tools: tools
});
// 多个工具调用
if (response.choices[0].message.tool_calls.length > 1) {
// 并行执行
const promises = response.choices[0].message.tool_calls.map(
toolCall => executeTool(
toolCall.function.name,
JSON.parse(toolCall.function.arguments)
)
);
const results = await Promise.all(promises);
}4. 安全考虑
javascript
class SecureToolExecutor {
constructor() {
this.allowedPaths = ['/data/public/', '/data/output/'];
this.maxFileSize = 10 * 1024 * 1024; // 10MB
}
validatePath(path) {
const resolved = path.resolve(path);
return this.allowedPaths.some(p => resolved.startsWith(p));
}
async executeFileTool(toolName, args) {
if (toolName === 'read_file' || toolName === 'write_file') {
if (!this.validatePath(args.path)) {
throw new Error('路径不在允许范围内');
}
}
return await executeTool(toolName, args);
}
}